/*
 *
 *  * Unidata Platform
 *  * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *  *
 *  * Commercial License
 *  * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *  *
 *  * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 *  * For clarification or additional options, please contact: info@unidata-platform.com
 *  * -------
 *  * Disclaimer:
 *  * -------
 *  * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 *  * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 *  * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 *  * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 *  * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 *
 */
package org.unidata.mdm.rest.v1.dq.core.service;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.cxf.jaxrs.ext.multipart.Attachment;
import org.apache.cxf.jaxrs.ext.multipart.Multipart;
import org.springframework.beans.factory.annotation.Autowired;
import org.unidata.mdm.core.context.ModelChangeContext.ModelChangeType;
import org.unidata.mdm.core.service.MetaModelService;
import org.unidata.mdm.core.util.SecurityUtils;
import org.unidata.mdm.dq.core.configuration.DataQualityDescriptors;
import org.unidata.mdm.dq.core.context.UpsertQualityModelContext;
import org.unidata.mdm.dq.core.type.model.instance.DataQualityInstance;
import org.unidata.mdm.dq.core.type.model.source.AbstractCleanseFunctionSource;
import org.unidata.mdm.dq.core.type.model.source.DataQualityModel;
import org.unidata.mdm.rest.system.ro.DetailedErrorResponseRO;
import org.unidata.mdm.rest.system.service.AbstractRestService;
import org.unidata.mdm.rest.v1.dq.core.exception.DataQualityRestExceptionIds;
import org.unidata.mdm.rest.v1.dq.core.ro.UploadQualityModelRO;
import org.unidata.mdm.system.exception.PlatformBusinessException;
import org.unidata.mdm.system.serialization.xml.XmlObjectSerializer;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;

/**
 * The Class CleanseFunctionRestService.
 *
 * @author Michael Yashin. Created on 19.05.2015.
 */
@Path(QualityModelRestService.SERVICE_PATH)
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public class QualityModelRestService extends AbstractRestService {
    /**
     * Service path.
     */
    public static final String SERVICE_PATH = "model";
    /**
     * Tag.
     */
    public static final String SERVICE_TAG = "model";
    /**
     * MMS instance.
     */
    @Autowired
    private MetaModelService metaModelService;

    @POST
    @Path("/import")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Operation(
        description = "Import full data quality model. Only xml is supported.",
        tags = { SERVICE_TAG },
        method = HttpMethod.POST,
        requestBody = @RequestBody(content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA), description = "Source request."),
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = UploadQualityModelRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "401"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response source(@Multipart(required = true, value = "file") Attachment attachment) throws IOException {

        if (Objects.isNull(attachment)) {
            return okOrNotFound(null);
        }

        if (!MediaType.TEXT_XML_TYPE.equals(attachment.getContentType())) {
            throw new PlatformBusinessException("Import of data quality failed. Invalid media type [{}]. XML is expected.",
                    DataQualityRestExceptionIds.EX_DQ_IMPORT_MODEL_TYPE_UNSUPPORTED, attachment.getContentType().toString());
        }

        DataQualityModel result = XmlObjectSerializer.getInstance()
                .fromXmlInputStream(DataQualityModel.class, attachment.getObject(InputStream.class));

        if (result == null) {
            throw new PlatformBusinessException("Import of data quality model failed. Empty definition.",
                    DataQualityRestExceptionIds.EX_DQ_IMPORT_MODEL_EMPTY_INPUT);
        }

        List<AbstractCleanseFunctionSource<?>> functions = new ArrayList<>(
                + result.getCompositeFunctions().size()
                + result.getJavaFunctions().size()
                + result.getGroovyFunctions().size()
                + result.getPythonFunctions().size());

        functions.addAll(result.getCompositeFunctions());
        functions.addAll(result.getJavaFunctions());
        functions.addAll(result.getGroovyFunctions());
        functions.addAll(result.getPythonFunctions());

        metaModelService.upsert(UpsertQualityModelContext.builder()
                .description(result.getDescription())
                .displayName(result.getDisplayName())
                .name(result.getName())
                .functionsUpdate(functions)
                .groupsUpdate(result.getFunctionGroup())
                .rulesUpdate(result.getRules())
                .setsUpdate(result.getSets())
                .assignmentsUpdate(result.getAssignments())
                .upsertType(ModelChangeType.FULL)
                .storageId(StringUtils.isNotBlank(result.getStorageId()) ? result.getStorageId() : SecurityUtils.getCurrentUserStorageId())
                .version(0) // Provoke force overwrite
                .force(true)
                .waitForFinish(true)
                .build());

        return ok(new UploadQualityModelRO(true));
    }

    @GET
    @Path("/export")
    @Produces(MediaType.TEXT_XML)
    @Operation(
        description = "Dump data quality model.",
        method = HttpMethod.GET,
        tags = { SERVICE_TAG },
        parameters = { @Parameter(description = "Storage ID. Optional", in = ParameterIn.QUERY, name = "storageId") },
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(type = "string", format = "binary")), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response dump(@QueryParam("storageId") String storageId) throws UnsupportedEncodingException {

        DataQualityInstance current = StringUtils.isBlank(storageId)
                ? metaModelService.instance(DataQualityDescriptors.DQ)
                : metaModelService.instance(DataQualityDescriptors.DQ, storageId, null);

        if (Objects.isNull(current) || current.isEmpty()) {
            return okOrNotFound(null);
        }

        DataQualityModel source = current.toSource();
        final String encodedFilename = URLEncoder.encode(
                "data-quality-" + DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd_HH-mm-ss") + ".xml",
                StandardCharsets.UTF_8.name());

        StreamingOutput result = output -> {
            try {
                output.write(
                    XmlObjectSerializer.getInstance()
                        .toXmlString(source, true)
                        .getBytes(StandardCharsets.UTF_8));
            } catch (Exception e) {
                throw new PlatformBusinessException("Data quality marshaling failed.",
                        DataQualityRestExceptionIds.EX_DQ_IMPORT_MODEL_MARSHALING_FAILED);
            }
        };

        return Response.ok(result)
                .encoding(StandardCharsets.UTF_8.name())
                .header("Content-Disposition", "attachment; filename=" + encodedFilename + "; filename*=UTF-8''" + encodedFilename)
                .header("Content-Type", MediaType.TEXT_XML)
                .build();
    }
}
